---
import { type CollectionEntry, getCollection } from "astro:content";
import { render } from "astro:content";
import Translations from "@components/Translations.astro";
import { toIso8601Full } from "@utils/datetime";
import ReadingTime from "@components/ReadingTime.astro";
import Keywords from "@components/Keywords.astro";
import Citations from "@components/Citations.astro";
import Signature from "@components/signature/Signature.astro";
import CopyrightNotice from "@components/CopyrightNotice.astro";
import { verifier as verifierPrototype } from "@lib/pgp/verify";
import { getSigners, isTranslation } from "@lib/collection/helpers";
import { get } from "@utils/anonymous";
import Authors from "@components/signature/Authors.astro";
import { getEntry } from "astro:content";
import Base from "@layouts/Base.astro";
import readingTime from "reading-time";
import type {
Entry,
MicroEntry,
OriginalEntry,
} from "@lib/collection/schemas";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<"blog">;
const post = Astro.props;
let original: OriginalEntry | MicroEntry;
if (isTranslation(post)) {
original = await getEntry(post.data.translationOf) as
| OriginalEntry
| MicroEntry;
if (!original) {
throw new Error(`Original post not found for ${post.id}`);
}
const originalAuthor = (original.data.signers ?? []).filter(
(s) => s.role === "author",
).map((s) => s.entity.id)?.[0];
const originalCoAuthors = new Set(
(original.data.signers ?? []).filter(
(s) => s.role === "co-author",
).map((s) => s.entity.id),
);
const translationAuthor = (post.data.signers ?? []).filter(
(s) => s.role === "author",
).map((s) => s.entity.id)?.[0];
const translationCoAuthors = new Set(
(post.data.signers ?? []).filter(
(s) => s.role === "co-author",
).map((s) => s.entity.id),
);
if (
(translationAuthor !== undefined &&
translationAuthor !== originalAuthor) ||
!translationCoAuthors.isSubsetOf(originalCoAuthors)
) {
throw new Error(
`Post ${post.id} has mismatched (co-)authors from original post ${original.id}`,
);
}
const translators = (post.data.signers ?? []).filter(
(s) => s.role === "translator",
).map((s) => s.entity.id);
for (const translator of translators) {
if (
originalAuthor === translator || originalCoAuthors.has(translator)
) {
throw new Error(
`Translator ${translator} in ${post.id} is already a (co-)author in original post`,
);
}
}
} else {
original = post;
if (post.data.signers?.some((x) => x.role === "translator")) {
throw new Error(
`Post ${post.id} is not a translation but has translators defined`,
);
}
}
// Add own post as a translation
const translationsSet = new Set(
(await getCollection(
"blog",
(x) =>
(x.data.kind === "translation") && x.data.translationOf.id ===
(post.data.kind === "translation"
? post.data.translationOf.id
: post.id),
) ?? []).map(({ id }) => id),
);
translationsSet.add(post.id);
const translations = [...translationsSet.values()].map((id) => ({
collection: post.collection,
id,
}));
const signers = await getSigners(post);
const verifier = await verifierPrototype.then((x) => x.clone());
// Add signers public keys to keyring
for (const { data } of signers.map(get("entity"))) {
if (data.publickey.armor !== undefined) {
verifier.addKeyFromArmor(data.publickey.armor);
}
}
const verification = post.filePath !== undefined
? await verifier.verify([
new URL(`file://${Deno.cwd()}/${post.filePath}`),
])
: undefined;
const { Content } = await render(post);
const { lang } = post.data;
const commit = await verification?.commit;
const reading = post.body ? readingTime(post.body, {}) : undefined;
const minutes = reading === undefined
? undefined
: Math.ceil(reading.minutes);
const estimative = reading === undefined
? undefined
: new Intl.DurationFormat(lang, {
style: "long",
}).format({ minutes });
const duration = minutes === undefined
? undefined
: `PT${Math.floor(minutes / 60) > 0 ? Math.floor(minutes / 60) + "H" : ""}${
minutes % 60 > 0 ? minutes % 60 + "M" : ""
}`;
const getOrUndefined = (k: string) =>
k in post.data ? post.data[k as keyof typeof post.data] : undefined;
const author = {
"@type": "Person",
} as const;
const contributor = post.data.signers.filter(({ role }) =>
role === "co-author"
).map(() => {
return {
"@type": "Person",
} as const;
});
const translator = post.data.signers.filter(({ role }) =>
role === "translator"
).map(() => {
return {
"@type": "Person",
} as const;
});
const JSONLD = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"@id": Astro.url.href,
articleBody: post.rendered?.html ?? post.body,
abstract: getOrUndefined("description"),
alternativeHeadline: getOrUndefined("subtitle"),
author,
citation: [].map(() => {
return {
"@type": "CreativeWork",
};
}),
contributor,
copyrightHolder: [author, ...contributor, ...translator],
// copyrightNotice: post.data.license, // WORKAROUND
copyrightYear: post.data.dateCreated.getFullYear(),
creativeWorkStatus: "Published",
dateCreated: post.data.dateCreated.toISOString(),
dateModified: "dateUpdated" in post.data
? post.data.dateUpdated?.toISOString()
: undefined,
// datePublished: undefined, // from git commit commit date
encodingFormat: "text/html",
headline: post.data.title,
inLanguage: post.data.lang,
isAccessibleForFree: true,
isBasedOn: isTranslation(post)
? {
"@type": "BlogPosting",
"@id": new URL(`blog/read/${post.data.translationOf}`, Astro.site).href,
}
: undefined,
keywords: original.data.keywords,
license: post.data.license, // WORKAROUND
locationCreated: {
"@type": "Place",
// XXX: getOrUndefined("locationCreated"),
},
mentions: [].map(() => {
return {
"@type": "Thing",
};
}),
// publication: {
// "@type": "PublicationEvent",
// }, // from git commit
// publisher: {
// "@type": "Person",
// }, // from git commit
text: post.rendered?.html ?? post.body,
timeRequired: post.body !== undefined ? duration : undefined,
translationOf: isTranslation(post)
? {
"@type": "BlogPosting",
"@id": new URL(`blog/read/${post.data.translationOf}`, Astro.site).href,
}
: undefined,
translator,
// version: undefined // TODO
wordCount: reading?.words,
workTranslations: translations.filter(({ id }) => id !== post.id).map((
{ id },
) => ({
"@type": "BlogPosting",
"@id": new URL(`blog/read/${id}`, Astro.site).href,
})),
description: getOrUndefined("description"),
name: post.data.title,
url: Astro.url.href,
} as const;
---
{post.data.title}
{
"subtitle" in post.data && (
{post.data.subtitle}
)
}
{
"description" in post.data && post.data.description &&
(
Resumo
{
post.data.description.split(new RegExp("\\s{2,}"))
.map((
x,
) => {x}
)
}
)
}
{verification && }
{
"keywords" in original.data && (
)
}
{
"relatedPosts" in original.data && (
)
}